在 昨天 Day 19,我們示範了 LangChain × MCP 的基礎整合:
Agent 能透過 MCP 呼叫單一工具(天氣查詢),並將 API 結果轉換成自然語言回覆。
但真實的應用往往不只需要一個工具。
例如要規劃旅行,Agent 可能同時需要「天氣資訊」與「景點推薦」:
這就需要 多工具協同。
今天,我們要讓 Agent 學會「自己思考」該用哪些工具、何時使用,
並透過 ReAct 策略(Reason + Act),讓多個 MCP 工具展開「智慧的對話」。
單一工具像是一把螺絲起子,能解決特定問題;
但要完成一個完整專案,往往得靠螺絲起子、鉗子、錘子…協作完成。
這時:
在昨天的範例中,Agent 是單次呼叫 MCP 工具,屬於「一次推理 → 一次行動」的單輪結構。
但當進入多工具情境時,我們需要一個更靈活的思考框架——這就是 ReAct 策略。
ReAct 的運作循環可以簡化為三個步驟:
T → A → O:Think → Act → Observe
get_weather
、get_attractions
)。這樣的循環可以重複多次,直到模型確認掌握足夠資訊,才進行最終回答。
舉例來說,在今天的「旅行規劃助理」中:
get_weather("維也納")
get_attractions("維也納")
這就是 ReAct 在多工具情境下的「思考-行動-觀察」循環。
更詳細的運作原理、Prompt 設計與內部狀態管理,可參考 基礎篇 Day 8 (ReAct)。
今天我們要打造一個「旅行規劃助手」:
繼昨天的天氣 MCP,我們再新增一個 MCP Server,這次改用 維基導遊 (Wikivoyage) 作為資料來源。
這個 API 能直接抓取城市條目的 HTML 內容,我們再透過 BeautifulSoup 解析「主要景點」章節。
pip install -q httpx beautifulsoup4
import httpx
from bs4 import BeautifulSoup
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("attractions-mcp")
WIKIVOYAGE_API = "https://zh.wikivoyage.org/w/api.php"
@mcp.tool()
async def get_attractions(city: str) -> dict:
"""查詢指定城市的熱門景點(使用 Wikivoyage 中文 API)。
Args:
city: 城市名稱
Returns:
景點列表(中文名稱)
"""
# Step 1. 取得城市的 Wikivoyage 頁面標題
search_params = {
"action": "parse",
"page": city,
"prop": "text",
"uselang": "zh-tw",
"format": "json"
}
async with httpx.AsyncClient(timeout=10.0) as client:
res = await client.get(WIKIVOYAGE_API, params=search_params)
parsed = res.json()
html = parsed.get("parse", {}).get("text", {}).get("*", "")
if not html:
return {"error": "無法取得頁面內容"}
# Step 3. 用 BeautifulSoup 抽取景點章節
soup = BeautifulSoup(html, "html.parser")
attractions = []
see_section = soup.find(id="主要景點")
if see_section:
for li in see_section.find_all_next("li", limit=20): # 抓前 20 筆
# 1) 名稱:span.listing-name
name_span = li.find("span", class_="listing-name")
name = name_span.get_text(strip=True) if name_span else li.get_text(strip=True)
# 2) 說明:蒐集 span.note(時間/價格/簡介等)
note_texts = [n.get_text(" ", strip=True) for n in li.select("span.note")]
info = " ".join(note_texts).strip()
# 3) 後備:若沒有 note,就用整段文字扣掉名稱
if not info:
whole = li.get_text(" ", strip=True)
info = whole.replace(name, "", 1).strip(" ,。;:-— ")
attractions.append({"name": name, "info": info})
return {"city": city, "attractions": attractions or ["暫時查不到景點"]}
if __name__ == "__main__":
mcp.run()
這次我們使用 httpx 而非傳統的 requests
,原因在於:
完成 Wikivoyage MCP 後,我們也可以直接在 Claude Desktop 中測試這個工具。
開啟設定選單,點選左下角的 Manage connectors,就能看到可用的 MCP 工具。
將 attractions
工具開啟後,Claude 就能直接呼叫這個 MCP Server。
圖:在 Claude Desktop 中啟用 attractions
MCP 工具後,Claude 可直接呼叫本地 MCP Server。
接著輸入像「維也納有哪些景點?」這樣的問題,就能看到 Claude 自動觸發 MCP 工具的請求與回傳結果。
圖:Claude Desktop 呼叫 Wikivoyage MCP 工具查詢維也納景點,MCP Server 回傳景點清單(例如美泉宮、皇宮與史蒂芬大教堂),Claude 將其轉換為自然語言回覆。
圖:Agent 先呼叫 Weather MCP 查天氣,再呼叫 Attractions MCP 查景點,最後整合成推薦行程。
同樣先安裝必要套件與設定環境變數:
pip install -q --pre -U langchain
pip install -q -U langchain-google-genai
pip install -q langchain-mcp-adapters
export GOOGLE_API_KEY={你的 Google API Key}
export ACCUWEATHER_API_KEY={你的 AccuWeather API Key}
接著在 LangChain 建立多工具 Agent:
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
import asyncio
import os
async def run_agent_with_multi_mcp():
# 從環境變數讀取 AccuWeather API Key
accuweather_api_key = os.getenv("ACCUWEATHER_API_KEY")
if not accuweather_api_key:
raise ValueError("請設定 ACCUWEATHER_API_KEY 環境變數")
# 建立 MultiServerMCPClient,並設定 AccuWeather MCP Server
client = MultiServerMCPClient(
{
"accuweather": {
"transport": "stdio",
"command": "uv",
"args": [
"--directory",
"{MCP 程式路徑}",
"run",
"weather_mcp_server.py"
],
"env": {"ACCUWEATHER_API_KEY": accuweather_api_key}
},
"attractions": {
"transport": "stdio",
"command": "uv",
"args": [
"--directory",
"{MCP 程式路徑}",
"run",
"attractions_mcp_server.py"
]
}
}
)
# 取得 MCP 工具
tools = await client.get_tools()
# 從環境變數讀取 Google API key
google_api_key = os.getenv("GOOGLE_API_KEY")
if not google_api_key:
raise ValueError("請設定 GOOGLE_API_KEY 環境變數")
# 建立 Gemini-2.5-FLASH LLM
llm = init_chat_model(
"gemini-2.5-flash",
model_provider="google_genai",
api_key=google_api_key
)
# 建立 Agent,並加入 MCP 工具
agent = create_agent(
model=llm,
tools=tools,
prompt="你是一位旅遊助理,可以根據天氣與景點資料,推薦最合適的維也納旅遊行程。"
)
# 與 Agent 對話,詢問維也納的推薦景點
response = await agent.ainvoke({
"messages": [{"role": "user", "content": "幫我依今天天氣推薦維也納景點"}]
})
print("\n=== 對話歷程 ===")
for m in response["messages"]:
role = m.__class__.__name__
print(f"[{role}] {getattr(m, 'content', '') or getattr(m, 'tool_calls', '')}")
print("\n=== 模型最終回答 ===")
print(response["messages"][-1].content)
if __name__ == "__main__":
asyncio.run(run_agent_with_multi_mcp())
執行後,Agent 會自動決定查詢順序並整合回答:
圖:旅行助理接收「幫我推薦維也納旅遊景點」後,先透過 Weather MCP 查得「多雲涼爽」,再用 Wikivoyage MCP 抽取主要景點。模型依據天氣,推薦皇宮與博物館等室內行程,也建議在多雲氣候下輕鬆散步的戶外選項,展現 ReAct 策略下的多工具協作。
這段輸出展示了 Agent 如何「先思考、再行動」:
這種「思考 → 工具呼叫 → 再思考 → 回覆」的互動過程,就是 ReAct 策略 的精髓。
Agent 不再只是被動查資料,而是真正具備「思考 + 行動 + 整合」的能力,展現出接近人類導遊的智慧。
今天,我們完成了從「單工具」到「多工具協同」的跨越:
這樣的設計讓 AI Agent 不再只是「回答問題」,而是能根據情境自主規劃、整合多來源資訊。